package com.tools.common.security;

import com.tools.common.container.r.OnlyReadArrayList;
import com.tools.common.exception.GetSystemInformationException;
import com.tools.common.exception.InvalidOperationException;
import com.tools.common.exception.NetOperationException;
import com.tools.common.io.CharsetEnum;
import com.tools.common.io.IOKit;
import com.tools.common.object.Note;
import com.tools.common.object.string.StrKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.*;
import java.nio.file.FileSystems;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 操作系统信息获取工具类
 * */
@Note("操作系统信息获取工具类")
public final class SysKit {

    private SysKit() {}

    @Note("日志输出对象")
    private static Logger LOGGER;

    @Note("本机第一个的 IPv4 地址")
    private static String LOCAL_FIRST_IPV4;

    @Note("本机第一个的 MAC 物理地址")
    private static String LOCAL_FIRST_MAC;

    @Note("本机所有的 MAC 物理地址")
    private static OnlyReadArrayList<String> LOCAL_ALL_MAC;

    @Note("本机所有的 IPv4 地址")
    private static OnlyReadArrayList<String> LOCAL_ALL_IPV4;

    @Note("关闭模式：用 Java 程序打开 cmd 命令行窗口，且执行完命令后会立刻关闭当前命令行窗口")
    public static final int CLOSE = 1;

    @Note("等待模式：用 Java 程序打开 cmd 命令行窗口，且执行完命令后不会关闭当前命令行窗口")
    public static final int KEEP = 2;

    @Note("窗口替换模式：用 Java 程序执行命令打开一个新的窗口，且会关闭当前的命令行窗口")
    public static final int START_CLOSE = 3;

    @Note("新窗口模式：用 Java 程序执行命令打开一个新的窗口，且不会关闭当前的命令行窗口")
    public static final int START_KEEP = 4;

    /* *******************************************************************************************
     *
     *          获取本机的一些默认方法
     *
     * *******************************************************************************************
     * */

    @Note("懒加载-本机所有的 MAC 物理地址，返回一个只读的 ArrayList 集合")
    public static OnlyReadArrayList<String> getAllLocalMacAddress() {
        if(LOCAL_ALL_MAC != null) return LOCAL_ALL_MAC;
        synchronized (SysKit.class) {
            if(LOCAL_ALL_MAC != null) return LOCAL_ALL_MAC;
            LOCAL_ALL_MAC = getAllLocalMacAddressRefresh();
            return LOCAL_ALL_MAC;
        }
    }

    @Note("懒加载-本机所有的 IPv4 物理地址，返回一个只读的 ArrayList 集合")
    public static OnlyReadArrayList<String> getAllIPv4Address() {
        if(LOCAL_ALL_IPV4 != null) return LOCAL_ALL_IPV4;
        synchronized (SysKit.class) {
            if(LOCAL_ALL_IPV4 != null) return LOCAL_ALL_IPV4;
            LOCAL_ALL_IPV4 = getAllIPv4AddressRefresh();
            return LOCAL_ALL_IPV4;
        }
    }


    @Note("懒加载-本机所有的第一个 MAC 物理地址返回")
    public static String getLocalFirstMAC() {
        if(LOCAL_FIRST_MAC != null) return LOCAL_FIRST_MAC;
        if(LOCAL_ALL_MAC != null) {
            String mac = LOCAL_ALL_MAC.get(0);
            if(mac != null) return mac;
        }
        synchronized (SysKit.class) {
            if(LOCAL_FIRST_MAC != null) return LOCAL_FIRST_MAC;
            LOCAL_FIRST_MAC = getMacAddressRefresh();
            return LOCAL_FIRST_MAC;
        }
    }


    @Note("懒加载-本机所有的第一个 IPv4 地址返回")
    public static String getLocalFirstIpv4() {
        if(LOCAL_FIRST_IPV4 != null) return LOCAL_FIRST_IPV4;
        synchronized (SysKit.class) {
            if(LOCAL_FIRST_IPV4 != null) return LOCAL_FIRST_IPV4;
            InetAddress localHost = getLocalHost();
            LOCAL_FIRST_IPV4 = (localHost == null) ? null : localHost.getHostAddress();
            return LOCAL_FIRST_IPV4;
        }
    }


    /* *******************************************************************************************
     *
     *          判断方法
     *
     * *******************************************************************************************
     * */


    @Note("判断操作系统是否是 Windows")
    public static boolean isWindows() {
        String os = getOperationSystemName();
        return os != null && os.contains("Windows");
    }


    @Note("判断操作系统是否是 Linux")
    public static boolean isLinux() {
        String os = getOperationSystemName();
        return os != null && os.contains("Linux");
    }


    @Note("判断操作系统是否是 Unix")
    public static boolean isUnix() {
        String os = getOperationSystemName();
        return os != null && os.contains("Unix");
    }



    public static boolean isLocalHost(String clientIp) {
        if("127.0.0.1".equals(clientIp) ||
                "0:0:0:0:0:0:0:1".equals(clientIp) ||
                "localhost".equals(clientIp)
        ) {
            return true;
        }
        InetAddress localHost = getLocalHost();
        return Objects.equals(clientIp, localHost.getHostAddress());
    }

    /* *******************************************************************************************
     *
     *          获取本地主机的信息
     *
     * *******************************************************************************************
     * */



    @Note("获取本地主机的网络地址信息实体 InetAddress")
    public static InetAddress getLocalHost() {
        InetAddress host;
        try {
            host = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            throw new GetSystemInformationException(e);
        }
        return host;
    }


    @Note("获取本地主机首选的 MAC 地址")
    public static String getMacAddressRefresh() {
        InetAddress localHost = getLocalHost();
        if(localHost == null) return "";
        String mac = private_getMac(localHost);
        if(mac != null) {
            return mac;
        }
        if(LOCAL_ALL_MAC == null || LOCAL_ALL_MAC.isEmpty()) return "";
        return LOCAL_ALL_MAC.get(0);
    }


    @Note("获取本机的所有的 MAC 物理地址")
    public static OnlyReadArrayList<String> getAllLocalMacAddressRefresh() {
        ArrayList<String> tempList = new ArrayList<>();
        Enumeration<NetworkInterface> networkInterfaces = private_getNetworkInterfaces();
        while (networkInterfaces.hasMoreElements()) {
            NetworkInterface network = networkInterfaces.nextElement();
            List<InterfaceAddress> interfaceAddresses = network.getInterfaceAddresses();
            for (InterfaceAddress ia : interfaceAddresses) {
                String mac = private_getMac(ia.getAddress());
                if(mac == null) continue;
                tempList.add(mac);
            }
        }
        return new OnlyReadArrayList<>(tempList.stream().distinct().collect(Collectors.toList()));
    }

    @Note("获取本机的所有的 IPv4 地址")
    public static OnlyReadArrayList<String> getAllIPv4AddressRefresh() {
        ArrayList<String> tempList = new ArrayList<>();
        Enumeration<NetworkInterface> networkInterfaces = private_getNetworkInterfaces();
        while (networkInterfaces.hasMoreElements()) {
            NetworkInterface network = networkInterfaces.nextElement();
            Enumeration<InetAddress> inetAddresses = network.getInetAddresses();
            while (inetAddresses.hasMoreElements()) {
                InetAddress inetAddress = inetAddresses.nextElement();
                if (inetAddress instanceof Inet4Address) {
                    tempList.add(inetAddress.getHostAddress());
                }
            }
        }
        return new OnlyReadArrayList<>(tempList);
    }

    /* *******************************************************************************************
     *
     *          IP 操作
     *
     * *******************************************************************************************
     * */

    @Note("把字符串 IPv4 地址转化为纯数字")
    public static int toNumberIP(String ip) {
        String[] array = StrKit.split(ip, ".");
        return (Integer.parseInt(array[0]) << 24) +
                (Integer.parseInt(array[1]) << 16) +
                (Integer.parseInt(array[2]) << 8) +
                Integer.parseInt(array[3]);
    }

    @Note("把 IP 地址对象转为纯数字")
    public static int toNumberIP(InetAddress address) {
        byte[] bytes = address.getAddress();
        return ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16)
                | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF);
    }

    @Note("把数字 IP 转化为字符串的 IPv4 地址，" +
            "第一段右移 24 位，第二段将高 8 位置 0，然后右移 16 位" +
            "第三段将高 16 位置 0，然后右移 8 位， 第四段将高 24 位置 0")
    public static String toStringIp(int ip) {
        return (ip >>> 24) +
                "." + ((ip & 0x00FFFFFF) >>> 16) +
                "." + ((ip & 0x0000FFFF) >>> 8) +
                "." + (ip & 0x000000FF);
    }

    public static String toStringIp(long ip) {
        return toStringIp((int) ip);
    }

    @Note("对 CIDR 的右半部分进行位移运算")
    public static int subnetBitsToMaskBit(int subnetBits) {
        return (0xFFFFFFFF << (32 - subnetBits));
    }

    public static int subnetBitsToMaskBit(String subnetBits) {
        return subnetBitsToMaskBit(Integer.parseInt(subnetBits));
    }

    @Note("判断 IP 地址是否位于指定的 CIDR 范围内")
    public static boolean ipInCIDR(int ip, String cidr) {
        try {
            String[] cidrParts = cidr.split("/");
            String network = cidrParts[0];
            int subnetBits = Integer.parseInt(cidrParts[1]);
            //转换为32位二进制数表示
            int networkInt = toNumberIP(network);
            //根据CIDR的子网掩码长度来计算网络部分
            int mask = subnetBitsToMaskBit(subnetBits);
            int networkPrefix = networkInt & mask;
            //判断IP是否在同一子网内
            return (ip & mask) == networkPrefix;
        } catch (Exception e) {
            throw new NetOperationException(e);
        }
    }

    public static boolean ipInCIDR(String ip, String cidr) {
        int intIP = toNumberIP(ip);
        return ipInCIDR(intIP, cidr);
    }

    public static boolean ipInCIDR(long ip, String cidr) {
        return ipInCIDR((int) ip, cidr);
    }

    @Note("判断 IP 地址是否位于指定的 【network + / +maskBits】 的 CIDR 范围内")
    public static boolean ipInCIDR(int ip, String network, int maskBits) {
        try {
            //转换为32位二进制数表示
            int networkInt = toNumberIP(network);
            //根据CIDR的子网掩码长度来计算网络部分
            int networkPrefix = networkInt & maskBits;
            //判断IP是否在同一子网内
            return (ip & maskBits) == networkPrefix;
        } catch (Exception e) {
            throw new NetOperationException(e);
        }
    }

    public static boolean ipInCIDR(String ip, String network, int maskBits) {
        int intIP = toNumberIP(ip);
        return ipInCIDR(intIP, network, maskBits);
    }

    public static boolean ipInCIDR(long ip, String network, int maskBits) {
        return ipInCIDR((int) ip, network, maskBits);
    }


    @Note("解析 CIDR 字符串为 IP 地址返回")
    public static int[] cidrToIntRange(String cidr) {
        String[] parts = cidr.split("/");
        String ip = parts[0];
        int prefixLength = Integer.parseInt(parts[1]);
        int mask = subnetBitsToMaskBit(prefixLength);
        int numberIP = toNumberIP(ip);
        //计算起始 IP 地址和结束 IP 地址
        int startIP = numberIP & mask;
        int endIP = startIP | (~mask);
        return new int[] {startIP, endIP};
    }

    public static long[] cidrToLongRange(String cidr) {
        int[] range = cidrToIntRange(cidr);
        return new long[] {range[0], range[1]};
    }

    @Note("解析 CIDR 字符串为 IP 地址返回")
    public static String[] cidrToStringRange(String cidr) {
        String[] parts = cidr.split("/");
        int prefixLength = Integer.parseInt(parts[1]);
        String ip = parts[0];
        int mask = subnetBitsToMaskBit(prefixLength);
        int numberIP = toNumberIP(ip);
        //计算起始 IP 地址和结束 IP 地址
        int startIP = numberIP & mask;
        int endIP = startIP | (~mask);
        String startIpStr = toStringIp(startIP);
        String endIpStr = toStringIp(endIP);
        return new String[] {startIpStr, endIpStr};
    }


    @Note("将IP地址转换为 32 位二进制字符串")
    public static String toIPBinaryString(String ip) {
        StringBuilder binary = new StringBuilder();
        String[] segments = ip.split("\\.");
        for (String segment : segments) {
            int num = Integer.parseInt(segment);
            String binarySegment = String.format("%08d", Integer.parseInt(Integer.toBinaryString(num)));
            binary.append(binarySegment);
        }
        return binary.toString();
    }

    /* *******************************************************************************************
     *
     *          获取操作系统信息数据操作
     *
     * *******************************************************************************************
     * */



    @Note("获取操作系统的名称，如【Windows 8.1】")
    public static String getOperationSystemName() {
        return System.getProperty("os.name");
    }


    @Note("获取操作系统的架构名称，如【x86】")
    public static String getOperationSystemArch() {
        return System.getProperty("os.arch");
    }


    @Note("获取操作系统的版本号，如【6.3】")
    public static String getOperationSystemVersion() {
        return System.getProperty("os.version");
    }



    @Note("获取 【操作系统的名称 | 架构名称 | 版本号】 的字符串返回")
    public static String getOperationSystemInfo() {
        return getOperationSystemName() + " | " + getOperationSystemArch() +
                " | " + getOperationSystemVersion();
    }


    @Note("获取操作系统默认的文件路径分隔符，Windows 是【\\】，Unix 是【/】")
    public static String getFileSeparator() {
        return FileSystems.getDefault().getSeparator();
    }



    @Note("获取操作系统默认的路径路径分隔符，Windows 是【;】，Unix 是【:】")
    public static String getPathSeparator() {
        return File.pathSeparator;
    }


    @Note("获取操作系统默认的路径路径分隔符，Windows 是【\\r\\n】，Unix 是【\\n】")
    public static String getLineSeparator() {
        return System.lineSeparator();
    }

    /* *******************************************************************************************
     *
     *          用 Java 程序执行操作系统命令
     *
     * *******************************************************************************************
     * */


    @Note("执行命令并返回执行 Process 对象，默认执行完后关闭当前命令行窗口")
    public static Process executeToProcess(String command) {
        return executeToProcess(command, CLOSE);
    }


    @Note("执行命令并返回执行 Process 对象，其中根据传入的 pattern 来决定命令行窗口的运行模式。在本工具类中为公共静态常量。")
    public static Process executeToProcess(String command, int pattern) {
        if(command == null || command.isEmpty()) throw new InvalidOperationException("即将要执行的命令不能为空");
        command = private_handleCommand(command, pattern);
        Process process = private_getProcessByExecuteCommand(command);
        private_lazyInitLogger();
        private_printExecSuccessLog(command);
        return process;
    }


    @Note("把一条命令分成数组的多个部分，然后传入执行，并返回 Process 对象")
    public static Process executeToProcess(String... command) {
        if(command == null || command.length == 0) throw new InvalidOperationException("即将要执行的命令为空");
        if(command.length == 1) return executeToProcess(command[0]);
        Process process = private_getProcessByExecuteCommand(command);
        String commandString = StrKit.toString(" ", command);
        private_lazyInitLogger();
        private_printExecSuccessLog(commandString);
        return process;
    }



    @Note("执行命令并返回执行结果，默认执行完后关闭当前命令行窗口")
    public static String executeCommand(String command) {
        return executeCommand(command, CLOSE);
    }


    @Note("执行命令并返回执行结果，根据传入的 pattern 来决定命令行窗口的运行模式")
    public static String executeCommand(String command, int pattern) {
        Process process = executeToProcess(command, pattern);
        return private_getExecuteCommandResult(process);
    }


    @Note("把一条命令分成数组的多个部分，然后传入执行")
    public static String executeCommand(String... command) {
        if(command == null || command.length == 0) return "";
        if(command.length == 1) return executeCommand(command[0]);
        Process process = private_getProcessByExecuteCommand(command);
        String commandString = StrKit.toString(" ", command);
        private_lazyInitLogger();
        private_printExecSuccessLog(commandString);
        return private_getExecuteCommandResult(process);
    }

    /* *******************************************************************************************
     *
     *          私有逻辑
     *
     * *******************************************************************************************
     * */

    @Note("获取本机所有网卡的实例，做一个非运行时异常异常捕获")
    private static Enumeration<NetworkInterface> private_getNetworkInterfaces() {
        try {
            return NetworkInterface.getNetworkInterfaces();
        } catch (SocketException e) {
            throw new GetSystemInformationException(e);
        }
    }


    @Note("获取单个 MAC 物理地址的逻辑")
    private static String private_getMac(InetAddress inetAddress) {
        try {
            StringBuilder builder = new StringBuilder();
            NetworkInterface network = NetworkInterface.getByInetAddress(inetAddress);
            if(network == null) return null;
            byte[] mac = network.getHardwareAddress();
            if(mac == null) return null;
            for (int i = 0; i < mac.length; i++) {
                builder.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : ""));
            }
            return builder.toString();
        } catch (SocketException e) {
            throw new GetSystemInformationException(e);
        }
    }


    @Note("使用双重检查锁来保证 Logger 对象懒加载时的线程安全")
    private static void private_lazyInitLogger() {
        if(LOGGER != null) return;
        synchronized (SysKit.class) {
            if(LOGGER != null) return;
            LOGGER = LoggerFactory.getLogger(SysKit.class);
        }
    }


    @Note("对命令做一些前缀和模式的处理")
    public static String private_handleCommand(String command, int pattern) {
        String prefix = isWindows() ? "cmd /" : "/bin/sh -";
        switch (pattern) {
            case CLOSE: prefix += 'c'; break;
            case KEEP: prefix += 'k'; break;
            case START_CLOSE: prefix += "c start "; break;
            case START_KEEP: prefix += "k start "; break;
            default: throw new InvalidOperationException("不知道 【" + pattern + "】 属于什么命令行执行模式");
        }
        if(command.startsWith("\"") && command.endsWith("\"")) {
            return prefix + command;
        }
        return prefix + "\"" + command + "\"";
    }


    @Note("执行操作系统命令，获取 Process 对象。注：command 要是完整的操作命令")
    private static Process private_getProcessByExecuteCommand(String command) {
        try {
            return Runtime.getRuntime().exec(command);
        } catch (IOException e) {
            throw new InvalidOperationException(e);
        }
    }

    @Note("执行操作系统命令，获取 Process 对象。注：commands 必须是一整个命令的分隔，分隔符是空格")
    private static Process private_getProcessByExecuteCommand(String[] commands) {
        try {
            return Runtime.getRuntime().exec(commands);
        } catch (IOException e) {
            throw new InvalidOperationException(e);
        }
    }


    @Note("执行命令操作后，将返回的输入流中的数据全部读取出来，转为字符串返回")
    private static String private_getExecuteCommandResult(Process process) {
        InputStream input = null;
        InputStream errorInput = null;
        String result;
        try{
            input = process.getInputStream();
            InputStreamReader inputReader = IOKit.inputStreamReader(input, CharsetEnum.GBK);
            result = IOKit.readToString(inputReader);
            if(result == null || result.isEmpty()) {
                errorInput = process.getErrorStream();
                InputStreamReader errorInputReader = IOKit.inputStreamReader(errorInput, CharsetEnum.GBK);
                result = IOKit.readToString(errorInputReader);
            }
        } catch (Exception e) {
            String message = e.getMessage();
            private_lazyInitLogger();
            LOGGER.error("执行命令出错，错误信息：【{}】", message);
            if(message != null && message.contains("系统找不到指定的文件")) {
                LOGGER.error("执行命令找不到指定路径，你可能需要加上路径参数，或者给命令加上 【cmd /XXX】 的前缀");
            }
            throw new InvalidOperationException(e);
        } finally {
            IOKit.close(input);
            IOKit.close(errorInput);
        }
        return result;
    }


    @Note("打印执行命令成功的日志")
    private static void private_printExecSuccessLog(String commandString) {
        LOGGER.info("执行命令 【{}】 成功！", commandString);
    }

}
